# apps/menus/tests.py
import json

from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework import status
from unittest.mock import patch

from apps.tenants.models import Tenant
from .models import Menu
from .serializers import MenuSerializer

User = get_user_model()


class MenuAPITestCase(APITestCase):
    def setUp(self):
        # 创建租户
        self.tenant = Tenant.objects.create(name="Test Tenant",code='test')

        # 创建管理员和普通用户
        self.admin = User.objects.create_superuser(
            username='admin',
            password='admin123',
            email='admin@test.com',
            tenant=self.tenant
        )
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123',
            full_name='Test User',
            role='teacher',
            email='test@test.com',
            tenant=self.tenant
        )

        # 普通用户仅用于权限测试
        self.other_tenant = Tenant.objects.create(name="Other Tenant", code='other')
        self.other_user = User.objects.create_user(
            username='otheruser',
            password='testpass123',
            full_name='Other User',
            role='student',
            email='other@test.com',
            tenant=self.other_tenant
        )

        # 登录 admin
        self.client.force_authenticate(user=self.admin)

        # 创建一些测试菜单
        self.menu1 = Menu.objects.create(
            title="Dashboard",
            name="Dashboard",
            path="/dashboard",
            component="Dashboard.vue",
            icon="ep:home",
            menu_type="menu",
            order=0,
            is_active=True,
            tenant=self.tenant
        )
        self.menu2 = Menu.objects.create(
            title="Users",
            name="UserManagement",
            path="/users",
            component="UserList.vue",
            icon="ep:user",
            menu_type="menu",
            parent=self.menu1,
            order=1,
            is_active=True,
            tenant=self.tenant
        )
        self.menu3 = Menu.objects.create(
            title="Settings",
            name="Settings",
            path="/settings",
            component="Settings.vue",
            icon="ep:setting",
            menu_type="menu",
            order=1,
            is_active=True,
            tenant=self.tenant
        )

        self.button1 = Menu.objects.create(
            title="Add User",
            name="UserCreate",
            menu_type="button",
            parent=self.menu2,
            order=0,
            is_active=True,
            tenant=self.tenant
        )

        # 给用户分配菜单权限
        self.menu1.roles.add(self.user)
        self.menu2.roles.add(self.user)

    def test_create_menu(self):
        """测试创建菜单"""
        url = reverse('menu-list')
        data = {
            'title': 'Reports',
            'name': 'Reports',
            'path': '/reports',
            'component': 'Reports.vue',
            'icon': 'ep:chart',
            'menu_type': 'menu',
            'parent': self.menu1.id,
            'order': 2,
            'is_active': True,
            'is_external': False,
            'role_ids': [self.user.id]
        }
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(Menu.objects.count(), 5)
        new_menu = Menu.objects.get(name='Reports')
        self.assertEqual(new_menu.roles.count(), 1)
        print(new_menu.tenant)
        self.assertEqual(new_menu.tenant, self.tenant)

    def test_update_menu(self):
        """测试更新菜单"""
        url = reverse('menu-detail', args=[self.menu1.id])
        data = {
            'title': 'Home Dashboard',
            'name': 'Dashboard',
            'path': '/home',
            'role_ids': [self.user.id]
        }
        response = self.client.put(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.menu1.refresh_from_db()
        self.assertEqual(self.menu1.title, 'Home Dashboard')
        self.assertEqual(self.menu1.path, '/home')
        self.assertEqual(self.menu1.roles.count(), 1)

    def test_delete_menu(self):
        """测试删除菜单"""
        url = reverse('menu-detail', args=[self.menu2.id])
        response = self.client.delete(url)
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
        self.assertFalse(Menu.objects.filter(id=self.menu2.id).exists())

    def test_list_menus_filtered(self):
        """测试过滤菜单"""
        url = reverse('menu-list') + '?menu_type=button'
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        self.assertEqual(response.data['results'][0]['name'], 'UserCreate')

    def test_only_admin_can_modify(self):
        """测试仅管理员可修改菜单"""
        # 切换为普通用户
        self.client.force_authenticate(user=self.user)
        url = reverse('menu-list')
        data = {'title': 'Hacked Menu', 'name': 'hacked'}
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_tenant_isolation(self):
        """测试租户数据隔离"""
        self.client.force_authenticate(user=self.other_user)
        url = reverse('menu-list')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_user_menu_endpoint(self):
        """测试 user_menu：获取当前用户菜单树"""
        self.client.force_authenticate(user=self.user)
        url = reverse('menu-user-menu')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        data = response.json()

        # 应包含 menu1 和 menu2，且 menu1 包含 menu2 作为 children
        self.assertEqual(len(data), 1)
        self.assertEqual(data[0]['name'], 'Dashboard')
        self.assertEqual(len(data[0]['children']), 1)
        self.assertEqual(data[0]['children'][0]['name'], 'UserManagement')

    def test_tree_action(self):
        """测试 tree：返回完整菜单树"""
        url = reverse('menu-tree')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        data = response.json()
        print(data)
        self.assertGreaterEqual(len(data), 2)
        # 检查层级结构
        root = data[0]
        if root['name'] == 'Dashboard':
            self.assertEqual(len(root['children']), 1)
            self.assertEqual(root['children'][0]['name'], 'UserManagement')

    def test_move_action(self):
        """测试 move：拖拽排序"""
        url = reverse('menu-move', args=[self.menu2.id])
        data = {
            'parent_id': None,
            'index': 0
        }
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.menu2.refresh_from_db()
        self.assertIsNone(self.menu2.parent)
        self.assertEqual(self.menu2.order, 0)

        # 验证同级排序
        self.menu1.refresh_from_db()
        self.assertEqual(self.menu1.order, 1)

    def test_export_action(self):
        """测试 export：导出菜单为 JSON"""
        url = reverse('menu-export')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response['Content-Type'], 'application/json')
        self.assertIn('attachment', response['Content-Disposition'])

        data = response.json()
        self.assertIsInstance(data, list)
        self.assertGreaterEqual(len(data), 3)
        names = [item['name'] for item in data]
        self.assertIn('Dashboard', names)
        self.assertIn('UserManagement', names)

    @patch('apps.menus.views.log_action')
    def test_import_data_action(self, mock_log):
        """测试 import_data：从 JSON 导入菜单"""
        url = reverse('menu-import')
        json_data = [
            {
                "title": "Imported Menu",
                "name": "ImportedMenu",
                "path": "/imported",
                "component": "Imported.vue",
                "icon": "ep:plus",
                "menu_type": "menu",
                "parent_name": "Dashboard",
                "order": 0,
                "is_active": True,
                "is_external": False
            }
        ]
        import_json = json.dumps(json_data)
        from io import BytesIO
        file_obj = BytesIO(import_json.encode('utf-8'))
        file_obj.name = 'menus.json'

        self.client.force_authenticate(user=self.admin)
        response = self.client.post(url, {'file': file_obj}, format='multipart')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['imported'], 1)
        self.assertTrue(Menu.objects.filter(name='ImportedMenu').exists())

        imported = Menu.objects.get(name='ImportedMenu')
        self.assertEqual(imported.parent, self.menu1)  # parent_name -> parent
        print(imported.tenant)
        print(self.user.tenant)
        self.assertEqual(imported.tenant, self.tenant)

        mock_log.assert_called()

    def test_routes_action(self):
        """测试 routes：生成 Vue Router 结构"""
        self.client.force_authenticate(user=self.user)
        url = reverse('menu-routes')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        routes = response.json()
        print( routes)

        # 应返回根菜单 Dashboard 及其子菜单
        self.assertEqual(len(routes), 1)
        route = routes[0]
        self.assertEqual(route['name'], 'Dashboard')
        self.assertEqual(route['path'], '/dashboard')
        self.assertEqual(route['component'], 'Dashboard.vue')
        self.assertEqual(route['meta']['title'], 'Dashboard')
        self.assertIn('permissions', route['meta'])
        self.assertIn('Dashboard', route['meta']['permissions'])

        # 子路由
        if 'children' in route:
            child = route['children'][0]
            self.assertEqual(child['name'], 'UserManagement')
            self.assertEqual(child['meta']['permissions'], ['UserManagement'])

    def test_serializer_children_excludes_buttons(self):
        """测试序列化器对按钮不返回 children 字段"""
        serializer = MenuSerializer(self.button1)
        data = serializer.data
        self.assertNotIn('children', data)

    def test_serializer_role_ids_write_only(self):
        """测试 role_ids 是 write-only 字段"""
        serializer = MenuSerializer(self.menu1)
        data = serializer.data
        self.assertNotIn('role_ids', data)
        self.assertIn('roles', data)